package org.wesc.boot.secure.shiro;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.wesc.boot.common.utils.HttpContextUtil;
import org.wesc.boot.common.utils.IpUtil;
import org.wesc.boot.dao.entity.Menu;
import org.wesc.boot.dao.entity.Role;
import org.wesc.boot.dao.entity.User;
import org.wesc.boot.dao.redis.RedisConstants;
import org.wesc.boot.dao.redis.RedisTemplateService;
import org.wesc.boot.secure.exception.IShiroConstants;
import org.wesc.boot.secure.exception.TokenTimeoutException;
import org.wesc.boot.secure.jwt.JwtToken;
import org.wesc.boot.secure.jwt.JwtUtil;
import org.wesc.boot.service.system.menu.MenuAdvanceService;
import org.wesc.boot.service.system.user.UserAdvanceService;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 自定义实现 ShiroRealm，包含认证和授权两大模块
 *
 * @author zhangwei
 */
@Slf4j
public class ShiroRealm extends AuthorizingRealm {

    private static final String USER_LOCKED = "1";

    private static final String SHIRO_REALM_NAME = "wesley_shiro_realm";

    @Autowired
    private RedisTemplateService redisTemplateService;

    @Autowired
    private UserAdvanceService userAdvanceService;

    @Autowired
    private MenuAdvanceService menuAdvanceService;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 授权模块，获取用户角色和权限
     *
     * @param token token
     * @return AuthorizationInfo 权限信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection token) {
        String username = JwtUtil.getUserName(token.toString());
        User user = userAdvanceService.findByUserName(username);

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        // 获取用户角色集
        List<Role> roleList = userAdvanceService.queryUserRoles(user);
        Set<String> roleSet = roleList.stream().map(Role::getRoleName).collect(Collectors.toSet());
        simpleAuthorizationInfo.setRoles(roleSet);

        // 获取用户菜单集
        List<Menu> menuList = menuAdvanceService.findUserMenuPerms(username);

        // 保存menu集
        Set<String> menuSet = menuList.stream().map(Menu::getPerms).collect(Collectors.toSet());
        simpleAuthorizationInfo.setStringPermissions(menuSet);
        return simpleAuthorizationInfo;
    }

    /**
     * 用户认证
     *
     * @param authenticationToken AuthenticationToken 身份认证token
     * @return AuthenticationInfo 身份认证信息
     * @throws AuthenticationException 认证相关异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 这里的 token是从 JWTFilter 的 executeLogin 方法传递过来的，已经经过了解密
        String token = (String) authenticationToken.getCredentials();

        // 获取ip
        HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
        String ip = IpUtil.getIpAddr(request);

        // 从 redis里获取这个 token
        String encryptToken = JwtUtil.encryptToken(token);
        String encryptTokenInRedis = null;
        try {
            encryptTokenInRedis = redisTemplateService.get(RedisConstants.TOKEN_CACHE_PREFIX + encryptToken + "." + ip).toString();
        } catch (Exception e) {
            log.warn(e.getMessage());
        }

        // 如果找不到，说明已经失效
        if (StringUtils.isBlank(encryptTokenInRedis)) {
            throw new TokenTimeoutException();
        }

        // 获取用户输入的用户名和密码
        String username = JwtUtil.getUserName(token);
        if (StringUtils.isBlank(username)) {
            throw new AuthenticationException(IShiroConstants.TOKEN_VERIFY_FAILED);
        }

        // 通过用户名到数据库查询用户信息
        User user = userAdvanceService.findByUserName(username);
        if (user == null) {
            throw new UnknownAccountException(IShiroConstants.UNKNOWN_ACCOUNT);
        }
        if (!JwtUtil.verify(token, username, user.getPassword())) {
            throw new IncorrectCredentialsException(IShiroConstants.USERNAME_PASSWORD_ERROR);
        }
        if (USER_LOCKED.equals(user.getLocked())) {
            throw new LockedAccountException(IShiroConstants.ACCOUNT_LOCKED);
        }

        return new SimpleAuthenticationInfo(token, token, SHIRO_REALM_NAME);
    }

}
